package com.cloud.redis.controller;

import com.cloud.redis.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RLock;
import org.redisson.api.RQueue;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

@RestController
@Slf4j
public class RedisController {
    @Autowired(required = false)
    private RedisUtil redisUtil;
    @Autowired(required = false)
    private RedisTemplate redisTemplate;
    @Autowired(required = false)
    private RedissonClient redissonClient;

    /**
     * 直连redis命令请看readme文档
     *
     * @return
     */
    @GetMapping("/redis")
    public String redis() {
        log.info("redis");
        return "redis";
    }

    /**
     * 对redis的key进行操作
     *
     * @return
     */
    @GetMapping("/redis/key")
    public void key() {
        // 1. 模糊查询键是否存在，使用需慎重，如果匹配的太多容易内存溢出
        // 其中的2，为每次扫描2个key，name是最终结果
        List<String> name = redisUtil.scan("name*", 2);
        System.out.println(name);
        // 2. 删除key
//        redisUtil.delete(name.get(0));

        Boolean test = redisUtil.hasKey("test");
        System.out.println("是否存在key:" + test);

        redisUtil.incrBy("test", 1);
    }

    @GetMapping("/redis/select")
    public void select() {
        // 1. 模糊查询键是否存在，使用需慎重，如果匹配的太多容易内存溢出
        // 其中的2，为每次扫描2个key，name是最终结果
        List<String> name = redisUtil.scan("name*", 2);
        System.out.println(name);
        // 2. 删除key
//        redisUtil.delete(name.get(0));

        Boolean test = redisUtil.hasKey("test");
        System.out.println("是否存在key:" + test);
        redisUtil.incrBy("test", 1);

        // 写入
        redisUtil.zAdd("test1", "test1", 2);
        redisUtil.zAdd("test1", "test2", 3);
        redisUtil.zAdd("test1", "test3", 4);
        // 增加分数
        redisUtil.zIncrementScore("test1", "test2", 10);
        // 获取指定value的评分
        System.out.println(redisUtil.zScore("test1", "test2"));

        // 根据score值获取集合元素数量
        System.out.println(redisUtil.zCount("test1", 0, 5));
        //根据Score值查询集合元素, 从大到小排序
        System.out.println("排序" + redisUtil.zReverseRangeByScore("test1", 0, 5));
        //返回元素在集合的排名,有序集合是按照元素的score值由小到大排列
        // 默认排名从0开始
        System.out.println("排名" + redisUtil.zRank("test1", "test1"));
        System.out.println("排名" + redisUtil.zRank("test1", "test2"));
        System.out.println("排名" + redisUtil.zRank("test1", "test3"));
    }

    /**
     * lua脚本做分布式锁
     */
    @GetMapping("/redis/lua")
    public void lua() {
        String key = "luaTest";
        String luaScript = "if redis.call('exists', KEYS[1]) == 0 then " +
                "redis.call('set', KEYS[1], ARGV[1]) " +
                "return 1 " +
                "else " +
                "return 0 " +
                "end";
        RedisScript<Long> redisScript = new DefaultRedisScript<>(luaScript, Long.class);
        Long execute = (Long) redisTemplate.execute(redisScript, Collections.singletonList(key), 10);
        if (1 == execute) {
            System.out.println("获取到锁");
        } else {
            System.out.println("获取锁失败");
        }
    }

    /**
     * redission
     */
    @GetMapping("/redis/redission")
    public void redission() throws InterruptedException {
        // rLock.lock() 和 rLock.lock(500, TimeUnit.SECONDS) 都会启用看门狗机制，自动续期。
        String KEY = "redission";
        RLock rLock = redissonClient.getLock(KEY);
        try {
            rLock.lock();
            Thread.sleep(30000);
            System.out.println("加锁成功");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rLock.unlock();
        }
        // 启用看门狗机制，自动续期。  100秒续约一次，相当于手动不释放锁则一直不释放
        rLock.lock(100, TimeUnit.SECONDS);
        Thread.sleep(30000);
//该方法尝试获取锁，如果锁当前可用，立即获取锁并返回 true，
// 如果不可用，会等待最多 20 秒。如果在等待时间内获取到锁，锁会持有 15 秒（即锁的过期时间）。如果在等待期间未能获取到锁，返回 false。
        rLock.tryLock(20, 15, TimeUnit.SECONDS);
    }

    /**
     * 切记  需要三个不同的配置，保证三个锁到三个实例上
     */
    @GetMapping("/redis/redLock")
    public void redLock1() throws InterruptedException {
//        Config config1 = new Config();
//        config1.useSingleServer().setAddress("redis://127.0.0.1:6379");
//        RedissonClient redissonClient1 = Redisson.create(config1);
//
//        Config config2 = new Config();
//        config2.useSingleServer().setAddress("redis://127.0.0.2:6379");
//        RedissonClient redissonClient2 = Redisson.create(config2);
//
//        Config config3 = new Config();
//        config3.useSingleServer().setAddress("redis://127.0.0.3:6379");
//        RedissonClient redissonClient3 = Redisson.create(config3);
//
//// 获取每个实例上的锁
//        RLock lock1 = redissonClient1.getLock("lock1");
//        RLock lock2 = redissonClient2.getLock("lock2");
//        RLock lock3 = redissonClient3.getLock("lock3");
//
//// 创建一个 RedLock 实例，使用多个锁
//
//        RedLock redLock = new RedLock(lock1, lock2, lock3);
//
//        try {
//            // 获取锁，并设置超时时间，防止死锁
//            boolean locked = redLock.lock(10, TimeUnit.SECONDS);
//            if (locked) {
//                // 执行需要加锁的代码
//                System.out.println("获得了锁，执行业务逻辑");
//                // 模拟业务逻辑
//                TimeUnit.SECONDS.sleep(5);
//            } else {
//                System.out.println("未获得锁");
//            }
//        } finally {
//            redLock.unlock();
//        }
    }

    /**
     * 事务
     * MULTI：开始一个事务块。
     * EXEC：执行事务块中的所有命令。
     * WATCH：监视某个或某些键，如果这些键发生了变化，事务将被中止。
     * DISCARD：取消事务，清空所有已排队的命令。
     */
    @GetMapping("/redis/multi")
    public void multi() {
//在 MULTI 之后，所有的命令会被缓存起来。EXEC 提交事务后 数据才会真正提交
//● WATCH balance 会监视 balance 键，如果在事务MULTI开始后，balance 键的值被其他客户端修改，EXEC 就会失败。
//● 如果 balance 在事务MULTI开始前,被其他客户端修改了，EXEC 返回空数组，表示事务没有执行。
//● 如果 balance 没有被修改，EXEC 会成功执行，SET 和 INCR 命令会按顺序执行。
    }

    @GetMapping("/redis/message")
    public void message() {
        // 延迟消息队列名称
        String DELAYED_QUEUE_NAME = "delayedQueue";
        RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(redissonClient.getQueue(DELAYED_QUEUE_NAME));
        delayedQueue.offer("你好啊", 10, TimeUnit.SECONDS);

    }

    @Scheduled(fixedRate = 5000) // 每 5 秒钟检查一次
    public void consumeDelayedMessages() {
        String DELAYED_QUEUE_NAME = "delayedQueue";
        if(redissonClient ==null){
            return;
        }
        RQueue<String> queue = redissonClient.getQueue(DELAYED_QUEUE_NAME);
        String message = queue.poll(); // 阻塞等待消息（最多等待 5 秒）

        if (message != null) {
            System.out.println("Consumed delayed message: " + message);
        }
    }
}
